#include "qae_qansidecoder.h"
#include "qae_qansibuffer.h"
#include <QtCore/QTextDecoder>
#include <QtCore/QByteArray>


namespace QAnsiEditor {


/************************************************************************/
/* All VT220 info can be found:                                         */
/*     http://vt100.net/docs/vt220-rm/chapter4.html                     */
/************************************************************************/

const QAnsiDecoder::State QAnsiDecoder::normalState[] = {
    { CHAR_BEL,     &QAnsiDecoder::bel,     normalState },
    { CHAR_BS,      &QAnsiDecoder::bs,      normalState },
    { CHAR_HT,      &QAnsiDecoder::ht,      normalState },
    { CHAR_LF,      &QAnsiDecoder::lf,      normalState },
    { CHAR_VT,      &QAnsiDecoder::vt,      normalState },
    { CHAR_FF,      &QAnsiDecoder::ff,      normalState },
    { CHAR_CR,      &QAnsiDecoder::cr,      normalState },
    { CHAR_ESC,     0,                      escapeState },
    { CHAR_NORMAL,  &QAnsiDecoder::normal,  normalState }
};

const QAnsiDecoder::State QAnsiDecoder::escapeState[] = {
    { '[',          &QAnsiDecoder::paramReset,      bracketState },
    { CHAR_ESC,     0,                              escapeState  },
    { 'D',          &QAnsiDecoder::index,           normalState  },
    { 'M',          &QAnsiDecoder::reverseIndex,    normalState  },
    //{ 'E',          &QAnsiDecoder::newLine,         normalState  },
    { '7',          &QAnsiDecoder::saveCursor,      normalState  },
    { '8',          &QAnsiDecoder::restoreCursor,   normalState  },
    { CHAR_NORMAL,  0,                              normalState  }
};

const QAnsiDecoder::State QAnsiDecoder::bracketState[] = {
    { '0',   &QAnsiDecoder::param,      bracketState },
    { '1',   &QAnsiDecoder::param,      bracketState },
    { '2',   &QAnsiDecoder::param,      bracketState },
    { '3',   &QAnsiDecoder::param,      bracketState },
    { '4',   &QAnsiDecoder::param,      bracketState },
    { '5',   &QAnsiDecoder::param,      bracketState },
    { '6',   &QAnsiDecoder::param,      bracketState },
    { '7',   &QAnsiDecoder::param,      bracketState },
    { '8',   &QAnsiDecoder::param,      bracketState },
    { '9',   &QAnsiDecoder::param,      bracketState },
    { ';',   &QAnsiDecoder::paramNext,  bracketState },

    { 'A',   &QAnsiDecoder::cursorUp,       normalState },
    { 'B',   &QAnsiDecoder::cursorDown,     normalState },
    { 'C',   &QAnsiDecoder::cursorForward,  normalState },
    { 'D',   &QAnsiDecoder::cursorBackward, normalState },
    { 'f',   &QAnsiDecoder::setCursor,      normalState },
    { 'H',   &QAnsiDecoder::setCursor,      normalState },
    { 's',   &QAnsiDecoder::saveCursor,     normalState },
    { 'u',   &QAnsiDecoder::restoreCursor,  normalState },
    { 'J',   &QAnsiDecoder::eraseScreen,    normalState },
    { 'K',   &QAnsiDecoder::eraseLine,      normalState },
    //{ 'X',   &QAnsiDecoder::eraseChar,      normalState },
    { 'L',   &QAnsiDecoder::insertLine,     normalState },
    { 'M',   &QAnsiDecoder::deleteLine,     normalState },
    //{ '@',   &QAnsiDecoder::insertChar,     normalState },
    //{ 'P',   &QAnsiDecoder::deleteChar,     normalState },

    { 'm',   &QAnsiDecoder::setAttributes,  normalState },

    { CHAR_BEL,     &QAnsiDecoder::bel,   bracketState },
    { CHAR_BS,      &QAnsiDecoder::bs,    bracketState },
    { CHAR_HT,      &QAnsiDecoder::ht,    bracketState },
    { CHAR_LF,      &QAnsiDecoder::lf,    bracketState },
    { CHAR_VT,      &QAnsiDecoder::vt,    bracketState },
    { CHAR_FF,      &QAnsiDecoder::ff,    bracketState },
    { CHAR_CR,      &QAnsiDecoder::cr,    bracketState },
    { CHAR_NORMAL,  0,                    normalState  }
};


QAnsiDecoder::QAnsiDecoder(QAnsiBuffer *pBuffer, QTextCodec *pCodec)
{
    m_pBuffer = pBuffer;
    m_pCodec = pCodec;
    m_pInternalState = new QTextCodec::ConverterState;
    m_pBuffer->setCurrentColor(DEFAULT_COLOR);
    m_pBuffer->setCurrentEffect(DEFAULT_EFFECT);
    m_pCurrentState = const_cast<State *>(normalState);
    m_bHackingAttribute = false;
    m_iCurrentParam = 0;
}

QAnsiDecoder::~QAnsiDecoder()
{
    delete m_pInternalState;
}

void QAnsiDecoder::decode(const char *pszData, int iLength)
{
    m_pszData = pszData;
    m_iDataIndex = 0;
    m_iDataLength = iLength;

    for ( ; m_iDataIndex < m_iDataLength; m_iDataIndex++) {
        /* m_pCurrentState points to normalState when initials */
        while (m_pCurrentState->ch != CHAR_NORMAL
               && m_pCurrentState->ch != m_pszData[m_iDataIndex]) {
            m_pCurrentState++;
        }
        if (m_pCurrentState->pfnAction != 0) {
            (this->*(m_pCurrentState->pfnAction))();
        }
        m_pCurrentState = const_cast<State *>(m_pCurrentState->pNextState);
    }
}

bool QAnsiDecoder::isPrint(const uchar uch)
{
    /*
     * Do not use QChar::isPrint(). It seems only work in unicode.
     */
    return uch >= 0x20;
}

void QAnsiDecoder::normal()
{
    /* Non-printable single-byte character */
    if (m_pInternalState->remainingChars == 0 && !isPrint(m_pszData[m_iDataIndex])) {
        return;
    }

    bool bRemainHackingAttribute = false;
    if (m_pInternalState->remainingChars != 0 && m_bHackingAttribute) {
        bRemainHackingAttribute = true;
        m_bHackingAttribute = false;
    }

    int n = 0;
    QString strRes = QString();
    /*
     * Append the character when:
     * - remaining characters != 0
     * - remaining characters == 0 && single-byte character is printable
     */
    while ((m_iDataIndex+n < m_iDataLength)
           && (m_pInternalState->remainingChars != 0 || isPrint(m_pszData[m_iDataIndex+n]))) {
        strRes += m_pCodec->toUnicode(m_pszData+m_iDataIndex+n, 1, m_pInternalState);
        n++;
        /* ESC sequence in the middle of a multi-byte character */
        if (m_pInternalState->remainingChars != 0) {
            int iIndex = m_iDataIndex + n;
            /* Pass through all ESCs */
            while (iIndex < m_iDataLength
                   && m_pszData[iIndex] == CHAR_ESC) {
                iIndex++;
            }
            if (m_pszData[iIndex] == '[') {
                m_bHackingAttribute = true;
                break;
            }
        }
    }

    m_pBuffer->appendTextContent(strRes);
    if (bRemainHackingAttribute) {
        m_pBuffer->restoreAttribute();  // fix attribute
    }
    if (m_bHackingAttribute) {
        m_pBuffer->saveAttribute();
    }

    /* We have m_iDataIndex++ in main decode loop, so just move (n-1). */
    m_iDataIndex += (n-1);
}

void QAnsiDecoder::bel()
{

}

void QAnsiDecoder::bs()
{
    m_pBuffer->moveCursor(-1, 0);
}

void QAnsiDecoder::ht()
{

}

void QAnsiDecoder::lf()
{
    /* \n */
#ifdef _USE_TERM
    if (m_pBuffer->getCursorY() == m_pBuffer->getRowCount()-1) {
        m_pBuffer->scrollLines(0, 1);
    } else {
        m_pBuffer->moveCursor(0, 1);
    }
#else
    m_pBuffer->setCursor(0, m_pBuffer->getCursorY());
    m_pBuffer->moveCursor(0, 1);
#endif
}

void QAnsiDecoder::vt()
{

}

void QAnsiDecoder::ff()
{
    m_pBuffer->eraseEntireScreen();
    m_pBuffer->setCursor(0, 0);
}

void QAnsiDecoder::cr()
{
    /* \r */
#ifdef _USE_TERM
    m_pBuffer->setCursor(0, m_pBuffer->getCursorY());
#else
    if (m_iDataIndex+1 < m_iDataLength
        && m_pszData[m_iDataIndex+1] == CHAR_LF) {
        // noop
    } else {
        m_pBuffer->setCursor(0, m_pBuffer->getCursorY());
        m_pBuffer->moveCursor(0, 1);
    }
#endif
}

void QAnsiDecoder::param()
{
    m_iCurrentParam = m_iCurrentParam*10 + (m_pszData[m_iDataIndex]-'0');
}

void QAnsiDecoder::paramNext()
{
    m_paramVector.append(m_iCurrentParam);
    m_iCurrentParam = 0;
}

void QAnsiDecoder::paramReset()
{
    m_iCurrentParam = 0;
    m_paramVector.clear();
}

void QAnsiDecoder::cursorUp()
{
    if (m_iCurrentParam == 0) {
        m_pBuffer->moveCursor(0, -1);
    } else {
        m_pBuffer->moveCursor(0, -m_iCurrentParam);
    }
}

void QAnsiDecoder::cursorDown()
{
    if (m_iCurrentParam == 0) {
        m_pBuffer->moveCursor(0, 1);
    } else {
        m_pBuffer->moveCursor(0, m_iCurrentParam);
    }
}

void QAnsiDecoder::cursorForward()
{
    if (m_iCurrentParam == 0) {
        m_pBuffer->moveCursor(1, 0);
    } else {
        m_pBuffer->moveCursor(m_iCurrentParam, 0);
    }
}

void QAnsiDecoder::cursorBackward()
{
    if (m_iCurrentParam == 0) {
        m_pBuffer->moveCursor(-1, 0);
    } else {
        m_pBuffer->moveCursor(-m_iCurrentParam, 0);
    }
}

void QAnsiDecoder::setCursor()
{
    int x = 1;
    int y = 1;
    if (m_paramVector.size() != 0) {
        x = m_paramVector[0];
    }
    if (m_iCurrentParam != 0) {
        y = m_iCurrentParam;
    }
    m_pBuffer->setCursor(y-1, x-1);
}

void QAnsiDecoder::saveCursor()
{
    m_pBuffer->saveCursor();
}

void QAnsiDecoder::restoreCursor()
{
    m_pBuffer->restoreCursor();
}

void QAnsiDecoder::eraseLine()
{
    int iOption = m_iCurrentParam;
    switch (iOption) {
    case 0:
        m_pBuffer->eraseToLineEnd();
        break;
    case 1:
        m_pBuffer->eraseToLineBegin();
        break;
    case 2:
        m_pBuffer->eraseEntireLine();
        break;
    default:
        break;
    }
}

void QAnsiDecoder::eraseScreen()
{
    int iOption = m_iCurrentParam;
    switch (iOption) {
    case 0:
        m_pBuffer->eraseToScreenEnd();
        break;
    case 1:
        m_pBuffer->eraseToScreenBegin();
        break;
    case 2:
        m_pBuffer->eraseEntireScreen();
        break;
    case 3:
        break;
    }
}

void QAnsiDecoder::insertLine()
{
    int iCount = (m_iCurrentParam==0) ? 1 : m_iCurrentParam;
    m_pBuffer->insertLine(iCount);
}

void QAnsiDecoder::deleteLine()
{
    int iCount = (m_iCurrentParam==0) ? 1 : m_iCurrentParam;
    m_pBuffer->deleteLine(iCount);
}

void QAnsiDecoder::index()
{
    if (m_pBuffer->getCursorY() == m_pBuffer->getRowCount()-1) {
        m_pBuffer->scrollLines(m_pBuffer->getCursorY(), 1);
    } else {
        m_pBuffer->moveCursor(0, 1);
    }
}

void QAnsiDecoder::reverseIndex()
{
    if (m_pBuffer->getCursorY() == 0) {
        m_pBuffer->scrollLines(m_pBuffer->getCursorY(), -1);
    } else {
        m_pBuffer->moveCursor(0, -1);
    }
}

void QAnsiDecoder::setAttributes()
{
    m_paramVector.append(m_iCurrentParam);

    char chColor = m_pBuffer->getCurrentColor();
    char chEffect = m_pBuffer->getCurrentEffect();
    for (int i = 0; i < m_paramVector.size(); i++) {
        if (m_paramVector[i] / 10 == 3) {  // foreground color
            chColor &= ~MASK_FG;
            chColor |= FG(m_paramVector[i] % 10);
        } else if (m_paramVector[i] / 10 == 4) {  // background color
            chColor &= ~MASK_BG;
            chColor |= BG(m_paramVector[i] % 10);
        } else {
            switch (m_paramVector[i]) {
            case 0:
                chColor = DEFAULT_COLOR;
                chEffect = DEFAULT_EFFECT;
                break;
            case 1:
                chEffect |= MASK_BOLD;
                break;
            case 2:
                chEffect |= MASK_FAINT;
                break;
            case 3:
                chEffect |= MASK_ITALIC;
                break;
            case 4:
                chEffect |= MASK_UNDERLINE;
                break;
            case 5:
                chEffect |= MASK_BLINK;
                break;
            case 6:
                chEffect |= MASK_RAPIDBLINK;
                break;
            case 7:
                chEffect |= MASK_REVERSE;
                break;
            case 8:
                chEffect |= MASK_INVISIBLE;
                break;
            default:
                break;
            }
        }
    }

    m_pBuffer->setCurrentColor(chColor);
    m_pBuffer->setCurrentEffect(chEffect);
}


}  // namespace QAnsiEditor
